﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;

namespace SmartMathLibrary.Storage
{
    /// <summary>
    /// Defines the search method for the binary search. 
    /// </summary>
    public enum BinarySearchMethod
    {
        /// <summary>
        /// This parameter defines that the binary search should search a minimum value.
        /// </summary>
        MinimumSearch = 0,
        /// <summary>
        /// This parameter defines that the binary search should search a maximum value.
        /// </summary>
        MaximumSearch = 1
    }

    /// <summary>
    /// This class is an abstract point storage, which implements special methods.
    /// </summary>
    [Serializable]
    public abstract class AbstractPointStorage : SmartMathLibrary.Storage.IAbstractPointStorage
    {
        /// <summary>
        /// This field saves the current cache position of the storage.
        /// </summary>
        protected int cachePointer;

        /// <summary>
        /// This field describes the maximum capacity of the storage.
        /// </summary>
        protected int cacheSize;

        /// <summary>
        /// This field describes the current fillsize of the storage.
        /// </summary>
        protected int fillSize;

        /// <summary>
        /// This field saves the position data of the storage.
        /// </summary>
        protected StoragePoint[] positionData;

        /// <summary>
        /// Initializes a new instance of the <see cref="AbstractPointStorage"/> class.
        /// </summary>
        /// <param name="size">The size of the abstract storage.</param>
        protected AbstractPointStorage(int size)
        {
            this.cacheSize = size;
            this.positionData = new StoragePoint[size];
        }

        /// <summary>
        /// Gets the size of the storage.
        /// </summary>
        /// <value>The size of the storage.</value>
        public int StorageSize
        {
            get { return cacheSize; }
        }

        /// <summary>
        /// Ascertained the largest x value of the storage.
        /// </summary>
        /// <value>The smallest x value of the storage.</value>
        public double MinX
        {
            get { return this[0].X; }
        }

        /// <summary>
        /// Ascertained the largest x value of the storage.
        /// </summary>
        /// <value>The largest x value of the storage.</value>
        public double MaxX
        {
            get { return this[this.cacheSize - 1].X; }
        }

        /// <summary>
        /// Gets the <see cref="SmartMathLibrary.Storage.StoragePoint"/> at the specified index.
        /// </summary>
        /// <value>The specified index.</value>
        public StoragePoint this[int index]
        {
            get
            {
                if (this.fillSize < this.cacheSize)
                {
                    return this.positionData[index];
                }

                return this.positionData[this.TransformElementIndex(index)];
            }
        }

        /// <summary>
        /// Transforms the current outer index to the internal index, where the position data is 
        /// really saved.
        /// </summary>
        /// <param name="index">The specified index to transform.</param>
        /// <returns>The internal index, which is used to access the position data.</returns>
        protected int TransformElementIndex(int index)
        {
            return (this.cachePointer + index) % this.cacheSize;
        }

        /// <summary>
        /// This method is a high performance search method, which selects a specified position range
        /// defined by the smallest and largest x value. If the specified values are not available in the
        /// storage, it will automatically search an approximate value.
        /// </summary>
        /// <param name="definition">The definition of the position range to select.</param>
        /// <returns>
        /// The indices of the nearest minimum and maximum x values in the current storage in form of a
        /// <see cref="StorageSelectionDefinition"/>.
        /// </returns>
        public StorageSelectionResult SelectPositionData(StorageSelectionDefinition definition)
        {
            return this.SelectPositionData(definition.MinX, definition.MaxX);
        }

        /// <summary>
        /// This method is a high performance search method, which selects a specified position range
        /// defined by the smallest and largest x value. If the specified values are not available in the
        /// storage, it will automatically search an approximate value.
        /// </summary>
        /// <param name="minX">The smallest x value for the selection.</param>
        /// <param name="maxX">The largest x value for the selection.</param>
        /// <returns>
        /// The indices of the nearest minimum and maximum x values in the current storage.
        /// </returns>
        public StorageSelectionResult SelectPositionData(double minX, double maxX)
        {
            StorageSelectionResult result = new StorageSelectionResult();

            if (this.fillSize <= 0)
            {
                return new StorageSelectionResult(0, 0);
            }

            result.FirstIndex = this.BinarySearch(minX, BinarySearchMethod.MinimumSearch);
            result.LastIndex = this.BinarySearch(maxX, BinarySearchMethod.MaximumSearch);

            return result;
        }

        /// <summary>
        /// This method provides a binary search for x values in the storage.
        /// </summary>
        /// <param name="value">The value or approximate value to search.</param>
        /// <param name="method">The binary search method.</param>
        /// <returns>The index position of the search value.</returns>
        private int BinarySearch(double value, BinarySearchMethod method)
        {
            int begin = 0;
            int middle = 0;
            int end = this.cacheSize;
            int result = -1;
            double a, b;
            double tempuri = 0;

            if (this.fillSize < this.cacheSize)
            {
                end = this.fillSize;

                if (this.positionData[this.fillSize - 1].X <= value)
                {
                    return this.fillSize - 1;
                }

                if (this.positionData[0].X >= value)
                {
                    return 0;
                }

                while ((begin <= end) && (result < 0))
                {
                    middle = (begin + ((end - begin) / 2));
                    tempuri = this.positionData[middle].X;

                    if (tempuri < value)
                    {
                        begin = middle + 1;
                    }
                    else if (tempuri > value)
                    {
                        end = middle - 1;
                    }

                    a = this.positionData[middle].X;
                    b = this.positionData[middle + 1].X;

                    if ((a <= value) && (value <= b))
                    {
                        if (a == value)
                        {
                            return middle;
                        }
                        else if (b == value)
                        {
                            return middle + 1;
                        }

                        if (method == BinarySearchMethod.MinimumSearch)
                        {
                            return middle + 1;
                        }
                        else
                        {
                            return middle;
                        }
                    }
                }

                return result;
            }
            else
            {
                if (this.positionData[this.TransformElementIndex(this.cacheSize - 1)].X <= value)
                {
                    return this.cacheSize - 1;
                }

                if (this.positionData[this.TransformElementIndex(0)].X >= value)
                {
                    return 0;
                }

                while ((begin <= end) && (result < 0))
                {
                    middle = (begin + ((end - begin) / 2));
                    tempuri = this.positionData[this.TransformElementIndex(middle)].X;

                    if (tempuri < value)
                    {
                        begin = middle + 1;
                    }
                    else if (tempuri > value)
                    {
                        end = middle - 1;
                    }

                    a = this.positionData[this.TransformElementIndex(middle)].X;
                    b = this.positionData[this.TransformElementIndex(middle + 1)].X;

                    if ((a <= value) && (value <= b))
                    {
                        if (a == value)
                        {
                            return middle;
                        }
                        else if (b == value)
                        {
                            return middle + 1;
                        }

                        if (method == BinarySearchMethod.MinimumSearch)
                        {
                            return middle + 1;
                        }
                        else
                        {
                            return middle;
                        }
                    }
                }

                return result;
            }
        }

        /// <summary>
        /// Gets the enumerator of the storage. This will be used to realize a for each loop.
        /// </summary>
        /// <returns>The current enumerator of the object.</returns>
        public IEnumerator<StoragePoint> GetEnumerator()
        {
            for (int i = 0; i < this.cacheSize; i++)
            {
                yield return this[i];
            }
        }
    }
}